﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Maps.MapControl;
using System.Globalization;
using Microsoft.Maps.MapControl.Design;
using System.Collections.ObjectModel;

using System.ServiceModel;

namespace MyPCTagging
{
    public partial class MainPage : UserControl
    {        
        // icons path
        private string dIcon = "Icon/device.png";
        private string gIcon = "Icon/group.png";
        // which device is parsing now
        private int dIndex = 0;
        // first node of TreeView
        private Node fNode = new Node();
        // list of selected nodes
        private List<Node> snList = new List<Node>();
        // time buffer 
        private List<string> tBuffer = new List<string>();
        // save all records
        private ObservableCollection<String> recordOC;
        // default colors for draw routes
        private Color[] myColors = {Colors.Red, Colors.Green, Colors.Blue, Colors.Orange, Colors.Purple};
        // automatically set view
        private double minLat, maxLat, minLng, maxLng;
        // old locations
        private List<Location> oldSet = new List<Location>();

        public MainPage()
        {
            InitializeComponent();
	        this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        /*
         * Handle for latest position button
         */
        private void LatestButton_Click(object sender, RoutedEventArgs e)
        {
            ObservableCollection<int> idOC = ButtonClickHelper();
            GetLatestRecords(idOC);
        }

        /*
         * Handle for route button
         */
        private void RouteButton_Click(object sender, RoutedEventArgs e)
        {
            ObservableCollection<int> idOC = ButtonClickHelper();
            GetAllRecords(idOC);
        }

        /*
         * Return ObservableCollection which contains selected nodes' id
         */ 
        private ObservableCollection<int> ButtonClickHelper()
        {
            // obtain selected devices
            ClearMyLayer();
            ClearMessage();
            snList.Clear();
            findChecked(fNode.Subcomponents[0], ref snList);

            // call WCF service
            ObservableCollection<int> oc = new ObservableCollection<int>();
            snList.ForEach(node => oc.Add(node.NodeID));
            return oc;
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            ClearMessage();
            LoadAllDevicesInfo();
        }

	    //遍历树找出被选中的节点
        private void findChecked(Node node, ref List<Node> list)
	    { 
            // device node and selected
            if (!node.IsGroup && node.ShouldInstall==true) 
		    {
                list.Add(node);

                // for debug
                //AddMessage("Selected "+node.NodeName);
                    
                return; 
            } 
            // group node
            else if (node.IsGroup)
            { 
                foreach (var item in node.Subcomponents) 
                { 
                    findChecked(item, ref list);  
                } 
            } 
        }     

        /*
         * Clear all points and routes
         */ 
        private void ClearMyLayer()
        {
            myLayer.Children.Clear();
        }

	    /*
         * Draw points on BingMaps
         * (If not support device name, use snList)
         */ 
        private void showPoints(List<Location> locaList, string deviceName) 
        {
            if (locaList==null || locaList.Count==0) return;
            AddMessage("Begin to draw points...");

            int pNum = 0;
            String name, tipText;
            for (int i = 0; i < locaList.Count; i++ )
            {
                if (locaList[i] == null)
                {
                    //AddMessage("Skip bad point...");
                    continue;
                }

                // prepare for automatically setting
                if (locaList[i].Latitude < minLat) minLat = locaList[i].Latitude;
                if (locaList[i].Latitude > maxLat) maxLat = locaList[i].Latitude;
                if (locaList[i].Longitude < minLng) minLng = locaList[i].Longitude;
                if (locaList[i].Longitude > maxLng) maxLng = locaList[i].Longitude;
                
                name = (deviceName == null) ? snList[i].NodeName : deviceName;
                
                // merge continuous same points while draw route
                tipText = name + " @ " + tBuffer[i];
                int dupNum = 0;
                if (deviceName != null)
                {
                    while (i + dupNum + 1 < locaList.Count
                        && locaList[i].Equals(locaList[i + dupNum + 1]))
                    {
                        dupNum++;
                        tipText += "\n" + name + " @ " + tBuffer[i + dupNum];
                    }
                }

                // add offset if needed
                for (int j=0; j<oldSet.Count; j++)
                {
                    if (oldSet[j]==locaList[i])
                    {
                        locaList[i].Latitude += 0.00005;
                        locaList[i].Longitude += 0.00005;
                        break;
                    }
                }

                // different kinds of pushpin style
                pNum++;
                //UIElement element = CreateSquareBlock(name+" @ "+tBuffer[i]);
                //UIElement element = CreateRedRectangle(name + " @ " + tBuffer[i]);
                UIElement element = CreatTooltipPushpin(pNum.ToString(), tipText);
                
                // may use different kinds of PositionOrgin
                myLayer.AddChild(element, locaList[i], PositionOrigin.BottomCenter);
                oldSet.Add(locaList[i]);
                i += dupNum;
            }

            // set zoom level and center automatically
            if (locaList.Count==1 && locaList[0]!=null)
            {
                viewMap.Center = locaList[0];
                viewMap.ZoomLevel = 10;
            } 
            else
            {
                double rate = 0.4, offset;
                LocationRect rect = new LocationRect();
                
                offset = maxLng - minLng;
                rect.East = Math.Min(maxLng+offset*rate, Location.MaxLongitude);
                rect.West = Math.Max(minLng-offset*rate, Location.MinLongitude);

                offset = maxLat - minLat;
                rect.North = Math.Min(maxLat+offset*rate, Location.MaxLatitude);
                rect.South = Math.Max(minLat-offset*rate, Location.MinLatitude);
                viewMap.SetView(rect);
            }
            
            AddMessage("...Finished drawing");
        } 
 
	    /*
         * Draw routes on BingMaps
         */ 
        private void showRout(List<Location> locaList)//返回颜色正常,坐标返回正常,绘图正常
        {
            if (locaList == null || locaList.Count == 0) return;
            AddMessage("Begin to draw routes...");

            LocationCollection points = skipPoints(locaList); 
            MapPolyline myLine = new MapPolyline(); 
            myLine.Locations = points; 
            myLine.StrokeThickness = 4; 
            
            /*Color myColor = new Color(); 
            myColor = Color.FromArgb(255, 255, 0, 0); 
            myLine.Stroke = new SolidColorBrush(myColor);*/
            myLine.Stroke = new SolidColorBrush(myColors[dIndex%myColors.Length]);

            myLayer.Children.Add(myLine);

            AddMessage("...Finished drawing");
        } 
 
	    /*
         * Translate List<Location> to LocationCollection
         * (Skip null value)
         */ 
        private LocationCollection skipPoints(List<Location> pointList)
        { 
            LocationCollection points = new LocationCollection(); 
            foreach (var point in pointList) 
            {
                if (point == null)
                {
                    //AddMessage("Skip a bad point...");
                    continue;
                }
                points.Add(point); 
            } 
            return points; 
        }  

	    /*
         * Create a 8x8 white block
         */ 
        private UIElement CreateSquareBlock(string tips) 
        {
            Canvas canvas = new Canvas();
            canvas.Background = new SolidColorBrush(Colors.White);
            canvas.Width = 8;
            canvas.Height = 8;

            Border border = new Border();
            border.BorderBrush = new SolidColorBrush(Colors.Black);
            border.BorderThickness = new Thickness(1);
            border.Child = canvas;

            ToolTipService.SetToolTip(canvas, tips);

            return border;
        }

        /*
         * Create a 50x80 red rectangle
         * (红红的一坨)
         */ 
        private UIElement CreateRedRectangle(string tips)
        {
            Canvas canvas = new Canvas();
            canvas.Width = 50;
            canvas.Height = 80;
            canvas.Background = new SolidColorBrush(Colors.Red);
            canvas.Opacity = 0.8;

            TextBlock block = new TextBlock();
            block.TextWrapping = TextWrapping.NoWrap;
            block.Opacity = 1;

            block.Width = 50;
            block.Height = 40;
            block.Margin = new Thickness(2, 4, 2, 0);
            block.TextWrapping = TextWrapping.Wrap;
            block.Text = tips;
            canvas.Children.Add(block);

            return canvas;
        }

        /*
         * Create a pushpin with tooltip
         */ 
        private Pushpin CreatTooltipPushpin(string pinContent, string tips)
        {
            Pushpin pushpin = new Pushpin();
            pushpin.FontSize = 10;
            pushpin.Content = pinContent;
            pushpin.Background = new SolidColorBrush(myColors[dIndex%myColors.Length]);
            
            //ToolTip tooltip = new ToolTip();
            //tooltip.Style = Application.Current.Resources["PushpinStyle"] as Style;
            //如何改变tooltip内容

            ToolTipService.SetToolTip(pushpin, tips);
            
            return pushpin;
        }

        /*
         * Add tips with a '\n'
         */ 
        private void AddMessage(string message)
        {
            showBlock.Text += message+"\n";
        }

        /*
         * Clear tips
         */ 
        private void ClearMessage()
        {
            showBlock.Text = "";
        }

        /*
         * Show error
         */
        private void showError(String content)
        {
            ChildWindow cw = new ChildWindow1(content);
            cw.Closed += new EventHandler(cw_Closed);
            cw.DataContext = cw;
            cw.Show();
        }

        /*
         * Handler for error window
         */ 
        void cw_Closed(object sender, EventArgs e)
        {
            // do nothing
        }

        /*
         * Reset view border
         */ 
        private void ResetViewBorder()
        {
            minLat = Location.MaxLatitude;
            maxLat = Location.MinLatitude;
            minLng = Location.MaxLongitude;
            maxLng = Location.MinLongitude;
        }

        //处理Checkbox点击事件
        private void ItemCheckbox_Click(object sender, RoutedEventArgs e)
        {
            TreeViewItem item = GetParentTreeViewItem((DependencyObject)sender);

            if (item != null)
            {
                Node feature = item.DataContext as Node;
                if (feature != null)
                {
                    UpdateChildrenCheckedState(feature);//更新子组件选中状态
                    UpdateParentCheckedState(item);//更新父组件选中状态
                }
            }
        }

        //静态方法：获取父级TreeViewItem
        private static TreeViewItem GetParentTreeViewItem(DependencyObject item)
        {
            if (item != null)
            {
                DependencyObject parent = VisualTreeHelper.GetParent(item);//获取依赖的父级对象
                TreeViewItem parentTreeViewItem = parent as TreeViewItem;//对象转换
                return (parentTreeViewItem != null) ? parentTreeViewItem : GetParentTreeViewItem(parent);//如果父级TreeViewItem存在则返回，否则就递归寻找
            }

            //找不到父对象，返回父对象不存在
            return null;
        }

        //静态方法：更新父级TreeViewItem选中状态
        private static void UpdateParentCheckedState(TreeViewItem item)
        {
            TreeViewItem parent = GetParentTreeViewItem(item);//获取父级TreeViewItem
            if (parent != null)//如果父对象不为空，为空则退出递归寻找
            {
                Node feature = parent.DataContext as Node;//对象转换
                if (feature != null)//如果对象不为空
                {
                    //更新子组件的选中状态
                    bool? childrenCheckedState = feature.Subcomponents.First<Node>().ShouldInstall;//得到第一个子组件的选中状态
                    for (int i = 1; i < feature.Subcomponents.Count(); i++)
                    {
                        if (childrenCheckedState != feature.Subcomponents[i].ShouldInstall)
                        {
                            childrenCheckedState = null;
                            break;
                        }
                    }

                    //将父组件的选中状态与子组件置为相同
                    feature.ShouldInstall = childrenCheckedState;

                    //继续递归搜索.
                    UpdateParentCheckedState(parent);
                }
            }
        }

        //用递归更新子组件的选中状态
        private static void UpdateChildrenCheckedState(Node feature)
        {
            if (feature.ShouldInstall.HasValue)
            {
                foreach (Node childFeature in feature.Subcomponents)
                {
                    childFeature.ShouldInstall = feature.ShouldInstall;
                    if (childFeature.Subcomponents.Count() > 0)
                    {
                        UpdateChildrenCheckedState(childFeature);
                    }
                }
            }
        }

        /*
         * Encapsulate WCF service operation "GetAllDeviceInfo"
         */
        public void LoadAllDevicesInfo()
        {
            AddMessage("Loading devices...");

            EndpointAddress address = new EndpointAddress(new Uri(Application.Current.Host.Source, "/DatabaseService.svc")); 
            DatabaseService.DatabaseServiceClient dbsClient = new DatabaseService.DatabaseServiceClient(new BasicHttpBinding(), address);
            dbsClient.GetAllDevicesInfoCompleted += callback_LoadAllDevicesInfoCompleted;
            dbsClient.GetAllDevicesInfoAsync();
        }

        /*
         * Callback function: load all devices information in TreeView
         */ 
        void callback_LoadAllDevicesInfoCompleted(object sender, DatabaseService.GetAllDevicesInfoCompletedEventArgs e)
        {
            Node rNode = new Node();
            if (e.Error==null && e.Result.name!=null)
            {
                LoadAllDevicesInfoHelper(ref rNode, e.Result);
                AddMessage("...Finished loading");
            }
            else
            {
                rNode.NodeName = "Failed to load";               
                AddMessage("...Error in LoadAllDevice");
                showError("Failed to load. Please check database connection.");
            }

            fNode.Subcomponents.Add(rNode);
            deviceTree.ItemsSource = fNode.Subcomponents;
        }
		
        /*
         * Helper function: transport message from DeviceNode to Node
         */ 
        private void LoadAllDevicesInfoHelper(ref Node node, DatabaseService.DeviceNode dNode)
        {
            // copy itself
            node.NodeID = dNode.id;
            node.NodeName = dNode.name;
            node.Description = "ID:" + dNode.id;
            if (dNode.type == DatabaseService.DeviceNodeType.Device)
            {
                node.IsGroup = false;
                node.Icon = dIcon;               
                return;
            }
            else
            {
                node.IsGroup = true;
                node.Icon = gIcon;
            }
            // copy children
            for (int i = 0; i < dNode.children.Count; i++ )
            {
                Node cNode = new Node();
                LoadAllDevicesInfoHelper(ref cNode, dNode.children[i]);
                
                node.Subcomponents.Add(cNode);
            }
	    }
    
        /*
         * Encapsulate WCF service operation "GetAllRecords"
         */ 
        public void GetAllRecords(ObservableCollection<int> idList)
        {
            AddMessage("Call GetAllRecords...");

            EndpointAddress address = new EndpointAddress(new Uri(Application.Current.Host.Source, "/DatabaseService.svc"));
            DatabaseService.DatabaseServiceClient dbsClient = new DatabaseService.DatabaseServiceClient(new BasicHttpBinding(), address);
            dbsClient.GetAllRecordsCompleted += callback_GetAllRecordsCompleted;
            dbsClient.GetAllRecordsAsync(idList);
        }

        /*
         * Callback function: return some devices record
         */ 
        void callback_GetAllRecordsCompleted(object sender, DatabaseService.GetAllRecordsCompletedEventArgs e)
        {
            // error
            if (e.Error != null)
            {
                AddMessage("...Error in GetAllRecords");
                return;
            }
            // no record
            if (e.Result == null || e.Result.Count == 0)
            {
                AddMessage("...Got no record");
                return;
            }
            // ok
            AddMessage("...Got some records");
            recordOC = e.Result;
            dIndex = -1;
            oldSet.Clear();
            ResetViewBorder();
            ParseNextRecord();                                    
        }

        private void ParseNextRecord()
        {
            dIndex++;
            if (dIndex == recordOC.Count)return;
            
            String time;
            String[] records, tokens;
            String allRecord = recordOC[dIndex];

            // check the whole record
            if (allRecord == null)
            {
                AddMessage("Skip a bad device...");
                ParseNextRecord();
                return;
            }

            int CID, LAC;
            ObservableCollection<int> cidList = new ObservableCollection<int>();
            ObservableCollection<int> lacList = new ObservableCollection<int>();

            // deal with each record
            tBuffer.Clear();
            records = allRecord.Split('&');

            int badNum = 0;
            for (int i = 0; i < records.Length; i++)
            {
                // separate CID and time
                tokens = records[i].Split('@');
                if (tokens.Length == 2)
                {
                    // store time
                    time = tokens[1];

                    // CID, LAC, MCC, MNC
                    tokens = tokens[0].Split('-');
                    if (tokens.Length == 4)
                    {
                        CID = Convert.ToInt32(tokens[0]);
                        LAC = Convert.ToInt32(tokens[1]);
                        //MCC = Convert.ToInt32(tokens[2]);
                        //MNC = Convert.ToInt32(tokens[3]);

                        cidList.Add(CID);
                        lacList.Add(LAC);
                        tBuffer.Add(time);
                        continue;
                    }
                }

                // find a bad record
                badNum++;
                tBuffer.Add("Bad time");
                cidList.Add(0);
                lacList.Add(0);
            }

            // all records of this device are bad
            if (badNum == records.Length)
            {
                AddMessage("Skip a bad device...");
                ParseNextRecord();
                return;
            }

            // call LocationService
            GetAllLocations(cidList, lacList);           
        }

        public void GetAllLocations(ObservableCollection<int> cidList, ObservableCollection<int> lacList)
        {
            AddMessage("Call GetAllLocations...");

            EndpointAddress address = new EndpointAddress(new Uri(Application.Current.Host.Source, "/LocationService.svc"));
            LocationService.LocationServiceClient locsClient = new LocationService.LocationServiceClient(new BasicHttpBinding(), address);
            locsClient.GetLocationsCompleted += callback_GetAllLocationsCompleted;
            locsClient.GetLocationsAsync(cidList, lacList);
        }

        void callback_GetAllLocationsCompleted(object sender, LocationService.GetLocationsCompletedEventArgs e)
        {
            // error
            if (e.Error != null)
            {
                AddMessage("...Error in GetAllLocations");
                return;
            }
            // no location
            if (e.Result == null || e.Result.Count == 0)
            {
                AddMessage("...Got no location");
                return;
            }
            // ok
            AddMessage("...Got some locations");

            int badNum = 0;
            List<Location> locList = new List<Location>();
            for (int i = 0; i < e.Result.Count; i++)
            {
                if (e.Result[i] == null)
                {
                    locList.Add(null);
                    badNum++;
                    continue;
                }
                locList.Add(new Location(e.Result[i].latitude, e.Result[i].longitude));
            }

            if (badNum == e.Result.Count)
            {
                AddMessage("...All locations are bad");
                return;
            }
            showRout(locList);
            showPoints(locList, snList[dIndex].NodeName);
            
            ParseNextRecord();
        }

        /*
         * Encapsulate WCF service operation "GetLatestRecords"
         */
        public void GetLatestRecords(ObservableCollection<int> idList)
        {
            AddMessage("Call GetLatestRecords...");

            EndpointAddress address = new EndpointAddress(new Uri(Application.Current.Host.Source, "/DatabaseService.svc"));
            DatabaseService.DatabaseServiceClient dbsClient = new DatabaseService.DatabaseServiceClient(new BasicHttpBinding(), address);
            dbsClient.GetLatestRecordsCompleted += callback_GetLatestRecordsCompleted;
            dbsClient.GetLatestRecordsAsync(idList);
        }

        /*
         * Callback function: return latest devices record
         */
        void callback_GetLatestRecordsCompleted(object sender, DatabaseService.GetLatestRecordsCompletedEventArgs e)
        {
            // error
            if (e.Error != null)
            { 
                AddMessage("...Error in GetLatestRecords");
                return;
            }
            // no record
            if (e.Result==null || e.Result.Count==0)
            {
                AddMessage("...Got no record");
                return;
            }
            // ok
            AddMessage("...Got some records");
            ParseLatestRecords(e.Result);
        }

        /*
         * Parse returned records and draw
         */ 
        private void ParseLatestRecords(ObservableCollection<String> recordOC)
        {
            int CID, LAC;
            String time;
            String[] tokens;
            ObservableCollection<int> cidList = new ObservableCollection<int>();
            ObservableCollection<int> lacList = new ObservableCollection<int>();

            int badNum = 0;
            tBuffer.Clear();
            dIndex = 0;
            foreach(String record in recordOC)
            {
                // try to parse
                if (record != null)
                {
                    tokens = record.Split('@');
                    if (tokens.Length == 2)
                    {
                        // store time
                        time = tokens[1];

                        // CID, LAC, MCC, MNC
                        tokens = tokens[0].Split('-');
                        if (tokens.Length == 4)
                        {
                            CID = Convert.ToInt32(tokens[0]);
                            LAC = Convert.ToInt32(tokens[1]);
                            //MCC = Convert.ToInt32(tokens[2]);
                            //MNC = Convert.ToInt32(tokens[3]);

                            cidList.Add(CID);
                            lacList.Add(LAC);
                            tBuffer.Add(time);
                            continue;
                        }
                    } 
                }

                // invalid record
                AddMessage("Skip a bad device...");
                badNum++;
                tBuffer.Add("Bad time");
                cidList.Add(0);
                lacList.Add(0);
            }

            if (badNum == recordOC.Count)
            {
                AddMessage("...All records are invalid");
                return;
            }
            
            // call LocationService
            GetLatestLocations(cidList, lacList);
        }

        /*
         * Encapsulate WCF service operation "GetLocations"
         */
        public void GetLatestLocations(ObservableCollection<int> cidList, ObservableCollection<int> lacList)
        {
            AddMessage("Call GetLatestLocations...");
            
            EndpointAddress address = new EndpointAddress(new Uri(Application.Current.Host.Source, "/LocationService.svc"));
            LocationService.LocationServiceClient locsClient = new LocationService.LocationServiceClient(new BasicHttpBinding(), address);
            locsClient.GetLocationsCompleted += callback_GetLatestLocationsCompleted;
            locsClient.GetLocationsAsync(cidList, lacList);
        }

        /*
         * Callback function: return locations and draw them
         */
        void callback_GetLatestLocationsCompleted(object sender, LocationService.GetLocationsCompletedEventArgs e)
        {
            // error
            if (e.Error != null)
            {
                AddMessage("...Error in GetLatestLocations");
                return;
            }
            // no location
            if (e.Result == null || e.Result.Count == 0)
            {
                AddMessage("...Got no location");
                return;
            }
            //ok
            AddMessage("...Got some locations");

            int badNum = 0;
            List<Location> locList = new List<Location>();
            for (int i = 0; i < e.Result.Count; i++)
            {
                if (e.Result[i] == null)
                {
                    locList.Add(null);
                    badNum++;
                    continue;
                }
                locList.Add(new Location(e.Result[i].latitude, e.Result[i].longitude));
            }

            if (badNum == e.Result.Count)
            {
                AddMessage("...All locations are bad");
                return;
            }
            ResetViewBorder();
            oldSet.Clear();
            showPoints(locList, null);
            dIndex++;
        }
    }
}
